package com.shop.loan.util;

import com.alibaba.fastjson.JSONObject;

import javax.crypto.Cipher;
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * RSA加密解密
 */
public final class RsaUtil {
    private static final String RSA = "RSA";
    private static final String SIGN_ALGORITHMS = "SHA1WithRSA";

    /**
     * 随机生成RSA密钥对(默认密钥长度为1024)
     *
     * @return
     */
    public static KeyPair generateRSAKeyPair() {
        return generateRSAKeyPair(1024);
    }

    /**
     * 随机生成RSA密钥对
     *
     * @param keyLength 密钥长度，范围：512～2048<br>
     *                  一般1024
     * @return
     */
    public static KeyPair generateRSAKeyPair(int keyLength) {
        try {
            KeyPairGenerator kpg = KeyPairGenerator.getInstance(RSA);
            kpg.initialize(keyLength);
            return kpg.genKeyPair();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static int getKeySize(PublicKey publicKey) {
        RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
        return rsaPublicKey.getModulus().bitLength();
    }

    public static int getKeySize(PrivateKey privateKey) {
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) privateKey;
        return rsaPrivateKey.getModulus().bitLength();
    }

    /**
     * 用公钥加密 <br>
     * 每次加密的字节数，不能超过密钥的长度值减去11
     *
     * @param data         需加密数据的byte数据
     * @param publicKeyStr 公钥
     * @return 加密后的byte型数据
     */
    public static byte[] encryptData(byte[] data, String publicKeyStr) {
        try {
            Cipher cipher = Cipher.getInstance(RSA);
            PublicKey publicKey = loadPublicKey(publicKeyStr);
            // 编码前设定编码方式及密钥
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            int keyBit = getKeySize(publicKey);
            int inputLen = data.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            int step = keyBit / 8 - 11;

            for (int i = 0; inputLen - offSet > 0; offSet = i * step) {
                byte[] cache;
                if (inputLen - offSet > step) {
                    cache = cipher.doFinal(data, offSet, step);
                } else {
                    cache = cipher.doFinal(data, offSet, inputLen - offSet);
                }

                out.write(cache, 0, cache.length);
                ++i;
            }

            byte[] encryptedData = out.toByteArray();
            out.close();
            return encryptedData;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 用私钥解密
     *
     * @param encryptedData 经过encryptedData()加密返回的byte数据
     * @param privateKeyStr 私钥
     * @return
     */
    public static byte[] decryptData(byte[] encryptedData, String privateKeyStr) {
        try {
            Cipher cipher = Cipher.getInstance(RSA);
            cipher.init(Cipher.DECRYPT_MODE, loadPrivateKey(privateKeyStr));
            int keyBit = getKeySize(loadPrivateKey(privateKeyStr));
            int inputLen = encryptedData.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            int step = keyBit / 8;

            for (int i = 0; inputLen - offSet > 0; offSet = i * step) {
                byte[] cache;
                if (inputLen - offSet > step) {
                    cache = cipher.doFinal(encryptedData, offSet, step);
                } else {
                    cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
                }

                out.write(cache, 0, cache.length);
                ++i;
            }

            byte[] decryptedData = out.toByteArray();
            out.close();
            return decryptedData;
        } catch (Exception e) {
            System.out.println(e);
            return null;
        }
    }

    /**
     * 通过公钥byte[](publicKey.getEncoded())将公钥还原，适用于RSA算法
     *
     * @param keyBytes 字节数组
     * @return
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static PublicKey getPublicKey(byte[] keyBytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        return keyFactory.generatePublic(keySpec);
    }

    /**
     * 通过私钥byte[]将公钥还原，适用于RSA算法
     *
     * @param keyBytes 私钥字节数组
     * @return PrivateKey
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static PrivateKey getPrivateKey(byte[] keyBytes) throws NoSuchAlgorithmException, InvalidKeySpecException {
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        return keyFactory.generatePrivate(keySpec);
    }

    /**
     * 使用N、e值还原公钥
     *
     * @param modulus        the modulus
     * @param publicExponent the public exponent
     * @return PublicKey
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static PublicKey getPublicKey(String modulus, String publicExponent)
            throws NoSuchAlgorithmException, InvalidKeySpecException {
        BigInteger bigIntModulus = new BigInteger(modulus);
        BigInteger bigIntPrivateExponent = new BigInteger(publicExponent);
        RSAPublicKeySpec keySpec = new RSAPublicKeySpec(bigIntModulus, bigIntPrivateExponent);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        PublicKey publicKey = keyFactory.generatePublic(keySpec);
        return publicKey;
    }

    /**
     * 使用N、d值还原私钥
     *
     * @param modulus         the modulus
     * @param privateExponent the private exponent
     * @return PrivateKey
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     */
    public static PrivateKey getPrivateKey(String modulus, String privateExponent)
            throws NoSuchAlgorithmException, InvalidKeySpecException {
        BigInteger bigIntModulus = new BigInteger(modulus);
        BigInteger bigIntPrivateExponent = new BigInteger(privateExponent);
        RSAPublicKeySpec keySpec = new RSAPublicKeySpec(bigIntModulus, bigIntPrivateExponent);
        KeyFactory keyFactory = KeyFactory.getInstance(RSA);
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        return privateKey;
    }

    /**
     * 从字符串中加载公钥
     *
     * @param publicKeyStr 公钥数据字符串
     * @throws Exception 加载公钥时产生的异常
     */
    public static PublicKey loadPublicKey(String publicKeyStr) throws Exception {
        try {
            byte[] buffer = Base64Utils.decode(publicKeyStr);
            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
            X509EncodedKeySpec keySpec = new X509EncodedKeySpec(buffer);
            return (RSAPublicKey) keyFactory.generatePublic(keySpec);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此算法");
        } catch (InvalidKeySpecException e) {
            throw new Exception("公钥非法");
        } catch (NullPointerException e) {
            throw new Exception("公钥数据为空");
        }
    }

    /**
     * 从字符串中加载私钥<br>
     * 加载时使用的是PKCS8EncodedKeySpec（PKCS#8编码的Key指令）。
     *
     * @param privateKeyStr 私钥
     * @return PrivateKey
     * @throws Exception
     */
    public static PrivateKey loadPrivateKey(String privateKeyStr) throws Exception {
        try {
            byte[] buffer = Base64Utils.decode(privateKeyStr);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
            KeyFactory keyFactory = KeyFactory.getInstance(RSA);
            return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此算法");
        } catch (InvalidKeySpecException e) {
            throw new Exception("私钥非法");
        } catch (NullPointerException e) {
            throw new Exception("私钥数据为空");
        }
    }

    /**
     * 从文件中输入流中加载公钥
     *
     * @param in 公钥输入流
     * @throws Exception 加载公钥时产生的异常
     */
    public static PublicKey loadPublicKey(InputStream in) throws Exception {
        try {
            return loadPublicKey(readKey(in));
        } catch (IOException e) {
            throw new Exception("公钥数据流读取错误");
        } catch (NullPointerException e) {
            throw new Exception("公钥输入流为空");
        }
    }

    /**
     * 从文件中加载私钥
     *
     * @param in 私钥文件名
     * @return 是否成功
     * @throws Exception
     */
    public static PrivateKey loadPrivateKey(InputStream in) throws Exception {
        try {
            return loadPrivateKey(readKey(in));
        } catch (IOException e) {
            throw new Exception("私钥数据读取错误");
        } catch (NullPointerException e) {
            throw new Exception("私钥输入流为空");
        }
    }

    /**
     * 读取密钥信息
     *
     * @param in 输入流
     * @return
     * @throws IOException
     */
    private static String readKey(InputStream in) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(in));
        String readLine = null;
        StringBuilder sb = new StringBuilder();
        while ((readLine = br.readLine()) != null) {
            if (readLine.charAt(0) != '-') {
                sb.append(readLine);
                sb.append('\r');
            }
        }

        return sb.toString();
    }

    /**
     * 使用私钥对数据进行加密签名
     *
     * @param encryptByte 数据
     * @param privateKey  私钥
     * @return 加密后的签名
     */
    public static String rsaSign(byte[] encryptByte, PrivateKey privateKey) {
        try {
            Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
            signature.initSign(privateKey);
            signature.update(encryptByte);
            byte[] signed = signature.sign();
            return Base64Utils.encode(signed);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 使用公钥判断签名是否与数据匹配
     *
     * @param encryptByte  数据
     * @param bs           签名
     * @param publicKeyStr 公钥
     * @return 是否篡改了数据
     */
    public static boolean doCheck(byte[] encryptByte, byte[] bs, String publicKeyStr) {
        try {
            Signature signature = Signature.getInstance(SIGN_ALGORITHMS);
            signature.initVerify(loadPublicKey(publicKeyStr));
            signature.update(encryptByte);
            boolean bverify = signature.verify(bs);
            return bverify;

        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    }

    /**
     * 打印公钥信息
     *
     * @param publicKey
     */
    public static void printPublicKeyInfo(PublicKey publicKey) {
        RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
        System.out.println("----------RSAPublicKey----------");
        System.out.println("Modulus.length=" + rsaPublicKey.getModulus().bitLength());
        System.out.println("Modulus=" + rsaPublicKey.getModulus().toString());
        System.out.println("PublicExponent.length=" + rsaPublicKey.getPublicExponent().bitLength());
        System.out.println("PublicExponent=" + rsaPublicKey.getPublicExponent().toString());
    }

    public static void printPrivateKeyInfo(PrivateKey privateKey) {
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) privateKey;
        System.out.println("----------RSAPrivateKey ----------");
        System.out.println("Modulus.length=" + rsaPrivateKey.getModulus().bitLength());
        System.out.println("Modulus=" + rsaPrivateKey.getModulus().toString());
        System.out.println("PrivateExponent.length=" + rsaPrivateKey.getPrivateExponent().bitLength());
        System.out.println("PrivatecExponent=" + rsaPrivateKey.getPrivateExponent().toString());
    }

    public final static String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCWV5w7dE3DpGErWAhqAr+MyF/ZvIBlu1ZivERnIb0fd+IxpWJTZWAuC6HCs1SzE7DpwgdtaeCruOMO6hT90r+Uz+uFy0E1IxZM0/2jUZhB5UuTGwmPHxiHNki1xo4OfQk+AqJeeKa9FBKtY1ozOcv8J39N5krYsn3jcaO6t3qk/wIDAQAB";

    public final static String PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJZXnDt0TcOkYStYCGoCv4zIX9m8gGW7VmK8RGchvR934jGlYlNlYC4LocKzVLMTsOnCB21p4Ku44w7qFP3Sv5TP64XLQTUjFkzT/aNRmEHlS5MbCY8fGIc2SLXGjg59CT4Col54pr0UEq1jWjM5y/wnf03mStiyfeNxo7q3eqT/AgMBAAECgYBxAd3P2vZxLuhTWlLIzNQLELrrwgOJdK3Ufcu08elk9Ao0YyproXIQanHnFkF+cjizHUPT/JgCvHEATvgEaxvRpge2hVXB1Gj0lduLREQmx1sF6PBL9rxn8QvyuoJlq7niqN5DZot3trSOcu0WeDuFiQ/GmPklAwNSmio3qWi4YQJBAOw4qC+mw4nBe+VAkh7Kpnzf/LF7Xu1DEmwpFXTrfxQH3+6Xr0BqMUw1KPVgn2nGJLB0IAHGYcQ5GndqFoNZKnkCQQCi7ig8yGW36j9TvbIBl+jHasKIVEgquQmAUg5koNgnKljAwSulNPUd89Yaif82ELmKE9wKacOUz5dMs/Za5W03AkA4BtgdNTh7m+B+52nA09/ZDmco0VIahCYCcVwddx2U6qk6Nc/jP0yh/pnQWTJ2LADXuTM+OIdHuyOD3y2nyaZBAkApD2PMjaQ0yS/UZsXpaOMc/mav/K2DNsaSNBu0eGSA2kcrrILXS5VKSxz16zkJ4wUfKwoVBbpoUjiK9/9pwM/ZAkEAh5lDs+QuQxgS6qHqbCEYk2LmevZPAIYpznTzE6KzPprBAZvD1vzbgQTi8L0SMxdQDCbpAf9DM3VM0z9T4a50+A==";

    /**
     * 组装http请求的入参
     *
     * @param data            http请求入参
     * @param encodePublicKey 加密公钥
     * @param signPrivateKey  加签私钥
     * @return
     */
    public static JSONObject encodeParam(JSONObject data, String encodePublicKey, String signPrivateKey) {
        // http请求入参组装
        JSONObject result = new JSONObject();
        // 业务参数json字符串组装
        String source = data.toString();
        try {
            // 加密部分demo
            // 从字符串中得到公钥
            byte[] encryptByte = RsaUtil.encryptData(source.getBytes("utf-8"), encodePublicKey);
            String encode = Base64Utils.encode(encryptByte);
            result.put("content", encode);
            // 从字符串中得到私钥
            PrivateKey privateKey = RsaUtil.loadPrivateKey(signPrivateKey);
            String signStr = RsaUtil.rsaSign(encryptByte, privateKey);
            result.put("sign", signStr);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    public static JSONObject decodeParam(JSONObject data, String signPublicKey, String decodePrivateKey) throws Exception {
        String afterencrypt = data.getString("content");
        String signStr = data.getString("sign");
        JSONObject result = null;
        try {
            // 验签
            boolean res = RsaUtil.doCheck(Base64Utils.decode(afterencrypt), Base64Utils.decode(signStr), signPublicKey);
            if (!res) {
                throw new Exception("验签失败");
            }
        } catch (Exception e) {
            throw new Exception("验签失败");
        }

        try {
            // 因为RSA加密后的内容经Base64再加密转换了一下，所以先Base64解密回来再给RSA解密
            byte[] decryptByte = RsaUtil.decryptData(Base64Utils.decode(afterencrypt), decodePrivateKey);
            String decryptStr = new String(decryptByte, "UTF-8");
            result = JSONObject.parseObject(decryptStr);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}
